/**
 * PETALS - PETALS Services Platform.
 * Copyright (c) 2006 EBM Websourcing, http://www.ebmwebsourcing.com/
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * -------------------------------------------------------------------------
 * $Id: MimeMessageManager.java 154 27 sept. 06 ofabre $
 * -------------------------------------------------------------------------
 */

package org.ow2.petals.bc.mail.listeners;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

import javax.activation.DataHandler;
import javax.jbi.JBIException;
import javax.jbi.component.ComponentContext;
import javax.jbi.messaging.DeliveryChannel;
import javax.jbi.messaging.InOnly;
import javax.jbi.messaging.MessageExchange;
import javax.jbi.messaging.NormalizedMessage;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;

import org.ow2.petals.component.framework.bc.AbstractBindingComponent;
import org.ow2.petals.component.framework.exception.PEtALSCDKException;
import org.ow2.petals.component.framework.util.SourceHelper;
import org.ow2.petals.component.framework.util.StringHelper;

/**
 * This class is used to process (reading or creating) mail messages
 *
 * @author ofabre - EBM Websourcing
 *
 */
public class MimeMessageManager {

    protected AbstractBindingComponent component;

    protected Logger log;

    protected String MEP = "mep";

    protected String IN_ONLY_MEP = "in-only";

    protected String ROBUST_IN_ONLY_MEP = "robust-in-only";

    protected String IN_OUT_MEP = "in-out";

    protected String IN_OPTIONAL_OUT_MEP = "in-optional-out";

    protected String OPERATION = "operation";

    /**
     *
     * @param context
     *            the {@link ComponentContext}
     * @param log
     *            the component {@link Logger}
     * @param component
     *            the component that instanciate this manager
     * @param channel
     *            the component {@link DeliveryChannel}
     */
    public MimeMessageManager(Logger log, AbstractBindingComponent component) {
        super();
        this.log = log;
        this.component = component;
    }

    /**
     * Create a {@link MimeMessage} from the given {@link NormalizedMessage},
     * including mail {@link Session} information.
     *
     * @param session
     *            the mail {@link Session}
     * @param internetAddress
     *            the recipient {@link InternetAddress}
     * @param in
     *            the mapped {@link NormalizedMessage}
     * @return the created {@link MimeMessage}
     * @throws JBIException
     *             if an error occurs during {@link MimeMessage} creation
     */
    public MimeMessage mapNormalizedMessageToMimeMessage(Session session,
        InternetAddress internetAddress, NormalizedMessage in)
        throws JBIException {

        MimeMessage mimeMessage = new MimeMessage(session);

        // Set MimeMessage attributes
        setMimeMessageAttributes(mimeMessage, internetAddress);

        Multipart multipart = new MimeMultipart();

        // Set MimeMessage body
        setMimeMessageBody(multipart, in);

        // Set MimeMessage attachements
        setMimeMessageAttachments(multipart, in);

        try {
            mimeMessage.setContent(multipart);
        } catch (javax.mail.MessagingException e) {
            String msg = "Error setting Mime message content "
                + "with all information collected from NormalizedMessage";
            log.severe(msg);
            throw new JBIException(msg, e);
        }

        return mimeMessage;
    }

    /**
     * Add {@link NormalizedMessage} attachments to {@link MimeMessage}
     * attachments. If an error occurs during the transmission of an attachment,
     * it is skipped and the process continues with other attachments.
     *
     * @param multipart
     *            the {@link Multipart} used to handle attachments
     * @param in
     *            the {@link NormalizedMessage} handling attachments that have
     *            to be transmitted
     */
    @SuppressWarnings("unchecked")
    protected void setMimeMessageAttachments(Multipart multipart,
        NormalizedMessage in) {
        Set<String> attachmentIds = in.getAttachmentNames();
        Set<DataHandler> attachments = new HashSet<DataHandler>();
        if (attachmentIds != null) {
            for (String id : attachmentIds) {
                attachments.add(in.getAttachment(id));
            }
        }
        if (attachments != null && !attachments.isEmpty()) {
            MimeBodyPart attachementBodyPart = null;
            for (DataHandler handler : attachments) {
                attachementBodyPart = new MimeBodyPart();
                try {
                    attachementBodyPart.setDataHandler(handler);
                    attachementBodyPart.setFileName(handler.getName());
                    multipart.addBodyPart(attachementBodyPart);
                } catch (javax.mail.MessagingException e) {
                    String msg = "Error setting MimeMessage attachments";
                    log.severe(msg);

                }
            }
        }
    }

    /**
     * Set {@link MimeMessage} recipient, subject and date.
     *
     * @param mimeMessage
     *            the {@link MimeMessage} to set
     * @param internetAddress
     *            the {@link InternetAddress} of the recipient
     * @throws JBIException
     *             if an error occurs during {@link MimeMessage} setting
     */
    protected void setMimeMessageAttributes(MimeMessage mimeMessage,
        InternetAddress internetAddress) throws JBIException {
        try {
            mimeMessage.setRecipient(Message.RecipientType.TO, internetAddress);

            mimeMessage.setSubject(component.getContext().getComponentName());

            mimeMessage.setSentDate(new Date());
        } catch (javax.mail.MessagingException e) {
            String msg = "Error setting MimeMessage attributes";
            log.severe(msg);
            throw new JBIException(msg, e);
        }
    }

    /**
     * Set the {@link MimeMessage} body from the {@link NormalizedMessage}
     * content.
     *
     * @param multipart
     *            the {@link Multipart} that will handle the
     *            {@link NormalizedMessage} content
     * @param in
     *            the {@link NormalizedMessage} that handle an xml content
     * @throws JBIException
     *             if the {@link Multipart} body setting failed
     */
    protected void setMimeMessageBody(Multipart multipart, NormalizedMessage in)
        throws JBIException {
        MimeBodyPart messageBodyPart = new MimeBodyPart();
        String content = null;
        try {
            /*if (in.getContent() != null) {
                content = SourceHelper.createString(in.getContent());
            }*/
        	TransformerFactory tFactory =
                TransformerFactory.newInstance();
            Transformer transformer = tFactory.newTransformer();
            StringWriter sw= new StringWriter();
            StreamResult result = new StreamResult(sw);
            transformer.transform(in.getContent(), result);
            content = sw.toString();
        } catch (Exception e) {
            throw new JBIException(e);
        }

        try {
            messageBodyPart.setContent(content, "text/plain");
            multipart.addBodyPart(messageBodyPart);
        } catch (javax.mail.MessagingException e) {
            throw new JBIException(e);
        }
    }

    /**
     * Create a {@link MessageExchange} from a mail {@link Message} and send it
     * to the component linked to the given address. Operation and Message
     * Exchange Pattern are specified in the mail {@link Message} subject,
     * following this pattern :
     * <p>
     * operation=<op_value>;mep=<mep_value> For the moment, only InOnly mep is
     * allowed. If no mep is specified, it is automaticaly set to InOnly
     *
     * @param message
     *            the mail {@link Message} to process
     * @throws JBIException
     *             if an error occurs during parameter retrieval from subject,
     *             during {@link MessageExchange} creation or during
     *             {@link MessageExchange} sending
     */
    public MessageExchange process(Message message) throws JBIException {
        // Retrieve Operation and MEP (Message Exchange Pattern) from subject.
        // Subject follows this pattern : operation=<op_value>&mep=<mep_value>
        String subject;
        try {
            subject = message.getSubject();
        } catch (MessagingException e1) {
            String msg = "Error retrieving subject of a mail";
            throw new JBIException(msg, e1);
        }
        String operation = StringHelper.extractValueForAttribute(subject,
            OPERATION, "&");
        String mep = StringHelper.extractValueForAttribute(subject, MEP, "&");

        // For the moment, MailBC only accepts InOnly MEP. If provided mep is
        // null or empty, it is automatically set to InOnly
        if (!IN_ONLY_MEP.equalsIgnoreCase(mep)) {
            if (!StringHelper.isNullOrEmpty(mep)) {
                String msg = "Message Exchange Pattern (MEP) not supported: "
                    + mep;
                throw new JBIException(msg);
            } else {
                mep = IN_ONLY_MEP;
            }
        }

        // Create jbi message content and attachments from mail message.
        // Recursively call the following method to treat multipart messages.
        StringBuffer jbiContent = new StringBuffer();
        Set<DataHandler> attachments = new HashSet<DataHandler>();
        try {
            createJBIContentAndAttachments(message, jbiContent, attachments);
        } catch (Exception e) {
            // Mail Message is regarded as a whole, so if an error occurs during
            // processing, the whole message is skipped
            String msg = "Error during mail message processing, "
                + "cannot create jbi content and attachment";
            throw new JBIException(msg, e);
        }

        // Create the JBI Message exchange
        MessageExchange exchange = null;
        try {
            exchange = createJBIMessageExchange(operation, mep, jbiContent
                .toString(), attachments);
        } catch (javax.jbi.messaging.MessagingException e) {
            String msg = "Error during creation of the JBI "
                + "message exchange from mail information";
            throw new JBIException(msg, e);
        }

        return exchange;
    }

    /**
     * Create a new Message exchange with the provided information. Create only
     * InOnly Message exchange for the moment.
     *
     * @param operation
     * @param mep
     * @param jbiContent
     * @param attachments
     * @return
     * @throws javax.jbi.messaging.MessagingException
     */
    protected MessageExchange createJBIMessageExchange(String operation,
        String mep, String jbiContent, Set<DataHandler> attachments)
        throws javax.jbi.messaging.MessagingException {
        MessageExchange message = null;
        if (IN_ONLY_MEP.equalsIgnoreCase(mep)) {
            message = createInOnlyMessageExchange(operation, jbiContent,
                attachments);
        }
        return message;
    }

    /**
     * Create an InOnly {@link MessageExchange}, then fill it with the given
     * information (operation, content and attachments).
     *
     * @param operation
     *            the service operation to invoke
     * @param jbiContent
     *            the {@link NormalizedMessage} content
     * @param attachments
     *            the {@link NormalizedMessage} attachments
     * @return the created InOnly {@link MessageExchange}
     * @throws javax.jbi.messaging.MessagingException
     *             if an error occured during {@link MessageExchange} creation
     *             and filling
     */
    protected InOnly createInOnlyMessageExchange(String operation,
        String jbiContent, Set<DataHandler> attachments)
        throws javax.jbi.messaging.MessagingException {

        // Create message exchange
        InOnly msg = component.getChannel().createExchangeFactory().createInOnlyExchange();

        // Create the "in" normalized message
        NormalizedMessage nm = msg.createMessage();

        // Fill content
        Source source;
        try {
            source = SourceHelper.createSource(jbiContent);
        } catch (PEtALSCDKException e) {
            throw new javax.jbi.messaging.MessagingException(
                "Can't create source from String content", e);
        }
        nm.setContent(source);

        // Fill attachments
        if (!attachments.isEmpty()) {
            for (DataHandler handler : attachments) {
                nm.addAttachment(handler.getName(), handler);
            }
        }

        // Add message to message exchange
        msg.setMessage(nm, "IN");

        // Set Operation
        if (!StringHelper.isNullOrEmpty(operation)) {
            msg.setOperation(new QName(operation));
        }

        return msg;
    }

    /**
     * Create jbi content (StringBuffer) and jbi attachments (Set<DataHandler>)
     * from a mail {@link Part}. If the given message {@link Part} is a
     * {@link Multipart}, all sub part are recursively processed.
     *
     * @param message
     *            the mail {@link Part} to process
     * @param jbiContent
     *            the resulting jbi content (a StringBuffer)
     * @param attachments
     *            the resulting jbi attachments (Set<DataHandler>)
     * @throws IOException
     *             if error occured during processing
     * @throws MessagingException
     *             if error occured during processing
     */
    protected void createJBIContentAndAttachments(Part message,
        StringBuffer jbiContent, Set<DataHandler> attachments)
        throws IOException, MessagingException {
        Object content = message.getContent();        
        if (content instanceof String) {
            jbiContent.append(content);
        } else if (content instanceof Multipart) {
            Multipart mp = (Multipart) content;
            int count = mp.getCount();
            for (int i = 0; i < count; i++) {
                createJBIContentAndAttachments(mp.getBodyPart(i), jbiContent,
                    attachments);
            }
        } else if (content instanceof InputStream) {
            attachments.add(message.getDataHandler());
        }
    }
}
